using System;
using System.Net.Sockets;

namespace Core;

public sealed class PacketDispatcher : IDisposable
{
    private bool _isDisposed;
    private readonly List<IPacketAwaitable> _waiters = new();

    public PacketAwaitable AddAwaitable(uint requestId)
    {
        var awaitable = new PacketAwaitable(requestId, this);

        lock (_waiters)
            _waiters.Add(awaitable);

        return awaitable;
    }

    public void CancelAll()
    {
        lock (_waiters)
        {
            for (int i = 0; i < _waiters.Count; i++)
            {
                _waiters[i].Cancel();
            }

            _waiters.Clear();
        }
    }

    public void Dispose()
    {
        Dispose(new ObjectDisposedException(nameof(PacketDispatcher)));
    }

    public void Dispose(Exception exception)
    {
        ArgumentNullException.ThrowIfNull(exception);

        lock (_waiters)
        {
            FailAll(exception);

            _isDisposed = true;
        }
    }

    public void FailAll(Exception exception)
    {
        ArgumentNullException.ThrowIfNull(exception);

        lock (_waiters)
        {
            for (int i = 0; i < _waiters.Count; i++)
            {
                _waiters[i].Fail(exception);
            }

            _waiters.Clear();
        }
    }

    public void RemoveAwaitable(IPacketAwaitable awaitable)
    {
        lock (_waiters)
            _waiters.Remove(awaitable);
    }

    public bool TryDispatch(BaseCommand command)
    {
        ArgumentNullException.ThrowIfNull(command);

        IPacketAwaitable? awaitable = null;

        lock (_waiters)
        {
            ThrowIfDisposed();

            for (var i = _waiters.Count - 1; i >= 0; i--)
            {
                awaitable = _waiters[i];

                if (awaitable.Identifier == command.RequestId)
                    break;

                _waiters.RemoveAt(i);
            }
        }

        if (awaitable == null )
            return false;

        awaitable.Complete(command);

        return true;
    }

    void ThrowIfDisposed()
    {
        if (_isDisposed)
            throw new ObjectDisposedException(nameof(PacketDispatcher));
    }
}